{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "ROD0y3TeNddr"
   },
   "source": [
    "# RadioML Receiver\n",
    "\n",
    "### A Recurrent Neural Network for Learning to demodulate and decode noisy signals over AWGN Channel.\n",
    "\n",
    "___"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "18u41hUUMZkz"
   },
   "source": [
    "# Environment Setup\n",
    "\n",
    "Download & install and import packages for this tutorial\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import local module\n",
    "import time\n",
    "import os\n",
    "import sys\n",
    "# a hack to import module from different directory\n",
    "module_path = os.path.abspath(os.path.join('..'))\n",
    "if module_path not in sys.path:\n",
    "    sys.path.append(module_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "base_uri": "https://localhost:8080/",
     "height": 52
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 11306,
     "status": "ok",
     "timestamp": 1532421239892,
     "user": {
      "displayName": "Dat Nguyen",
      "photoUrl": "//lh3.googleusercontent.com/-irIcNYd-KIw/AAAAAAAAAAI/AAAAAAAAAEs/NlM8kG6RL4Q/s50-c-k-no/photo.jpg",
      "userId": "108917076199533451784"
     },
     "user_tz": 420
    },
    "id": "sHIc3Fq1NLRr",
    "outputId": "66bb06bb-e5a8-490b-c8e1-6198548ef382"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Environment setup is completed.\n"
     ]
    }
   ],
   "source": [
    "import multiprocessing as mp\n",
    "import random \n",
    "import numpy as np\n",
    "import commpy as cp\n",
    "from commpy.channelcoding import Trellis\n",
    "from commpy.modulation import QAMModem, PSKModem\n",
    "\n",
    "# For baseline comparision\n",
    "from radioml.metrics import get_ber_bler\n",
    "from radioml.models import Baseline \n",
    "\n",
    "# For data generator\n",
    "from radioml.dataset import RadioDataGenerator\n",
    "\n",
    "# For visualization\n",
    "import pylab\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.gridspec as gridspec\n",
    "import seaborn as sns\n",
    "sns.set()\n",
    "\n",
    "print(\"\\nEnvironment setup is completed.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "uUkGsBo8Y5XW"
   },
   "source": [
    "# Define parameters for this experiment"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     }
    },
    "colab_type": "code",
    "id": "-4vqq3NBY5Xa"
   },
   "outputs": [],
   "source": [
    "DATA_LENGTH  = 100\n",
    "DATA_RATE    = 1/2\n",
    "SNR_TRAIN    = 8.0 \n",
    "\n",
    "modem = QAMModem(4) \n",
    "trellis = Trellis(memory = np.array([2]),\n",
    "                  g_matrix = np.array([[0o7, 0o5]]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "ZO0F4ydKP_SE"
   },
   "source": [
    "## Create training data generator and validation set"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 577 ms, sys: 257 ms, total: 834 ms\n",
      "Wall time: 4.77 s\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%%time\n",
    "radio = RadioDataGenerator(DATA_LENGTH, 40, 1, modulation_scheme='QPSK')\n",
    "\n",
    "training_generator   = radio.ecc_data_generator(1/1000, SNR_TRAIN, batch_size=256, num_cpus=16, seed=None)\n",
    "x_dev, y_dev =  next(radio.ecc_data_generator(1/1000, SNR_TRAIN, batch_size=12800, num_cpus=16, seed=2018))\n",
    "\n",
    "## Visualize samples\n",
    "equalized_data, data_estimate = next(training_generator)\n",
    "plt.scatter(equalized_data.reshape((-1, 2))[...,0],\n",
    "           equalized_data.reshape((-1, 2))[...,1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "fzIgWXzhY5ZN"
   },
   "source": [
    "## Measure baseline performance (Classic Demodulation + Viterbi)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "base_uri": "https://localhost:8080/",
     "height": 52
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 3324,
     "status": "ok",
     "timestamp": 1532421247835,
     "user": {
      "displayName": "Dat Nguyen",
      "photoUrl": "//lh3.googleusercontent.com/-irIcNYd-KIw/AAAAAAAAAAI/AAAAAAAAAEs/NlM8kG6RL4Q/s50-c-k-no/photo.jpg",
      "userId": "108917076199533451784"
     },
     "user_tz": 420
    },
    "id": "UrXMpwthY5ZR",
    "outputId": "399bdca8-5c57-4697-c4f9-9a95b7ee56ce"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Baseline BER: 0.0007\n",
      "Baseline BLER: 0.04805\n"
     ]
    }
   ],
   "source": [
    "baseline_receiver = Baseline(modulation_scheme='QPSK')\n",
    "\n",
    "with mp.Pool(mp.cpu_count()) as pool:\n",
    "    baseline_estimated = pool.map(baseline_receiver, [i.view(complex) for i in x_dev])\n",
    "    data_estimate = np.array(baseline_estimated)\n",
    "    data_original = np.squeeze(y_dev, -1)  # (batch, 200, 1) to (batch, 200)\n",
    "    \n",
    "ber, bler = get_ber_bler(data_estimate, \n",
    "                         data_original)\n",
    "print('Baseline BER: %.4f' % ber)\n",
    "print('Baseline BLER: %.5f' % bler)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "i0C-2QYyY5Zf"
   },
   "source": [
    " # Construct a Recurrent  Neural Network \n",
    " \n",
    "Here we implement an RNN that will read encoded complex inputs and estimate the orignal message bits"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "base_uri": "https://localhost:8080/",
     "height": 351
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 1923,
     "status": "ok",
     "timestamp": 1532421250046,
     "user": {
      "displayName": "Dat Nguyen",
      "photoUrl": "//lh3.googleusercontent.com/-irIcNYd-KIw/AAAAAAAAAAI/AAAAAAAAAEs/NlM8kG6RL4Q/s50-c-k-no/photo.jpg",
      "userId": "108917076199533451784"
     },
     "user_tz": 420
    },
    "id": "JcozPuNKY5Zh",
    "outputId": "515a2d96-b85c-45ef-9d8c-0a630e6904a5"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "input_1 (InputLayer)         (None, None, 2)           0         \n",
      "_________________________________________________________________\n",
      "bidirectional (Bidirectional (None, None, 400)         324800    \n",
      "_________________________________________________________________\n",
      "bidirectional_1 (Bidirection (None, None, 400)         961600    \n",
      "_________________________________________________________________\n",
      "time_distributed (TimeDistri (None, None, 1)           401       \n",
      "=================================================================\n",
      "Total params: 1,286,801\n",
      "Trainable params: 1,286,801\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "# For end2end receiver\n",
    "import tensorflow as tf\n",
    "from tensorflow.keras.layers import Input, Dense, BatchNormalization\n",
    "from tensorflow.keras.layers import LSTM, Bidirectional, TimeDistributed\n",
    "\n",
    "# To avoid accidenttally run this cell multiple times.\n",
    "tf.keras.backend.clear_session()\n",
    "\n",
    "inputs  = Input(shape=(None, 2))\n",
    "x = Bidirectional(LSTM(200, return_sequences=True))(inputs)\n",
    "x = Bidirectional(LSTM(200, return_sequences=True))(x)\n",
    "outputs = TimeDistributed(Dense(1, activation='sigmoid'))(x)\n",
    "\n",
    "model = tf.keras.Model(inputs, outputs)\n",
    "model.summary()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "LaaMODV3UnwF"
   },
   "source": [
    "## Define Loss Function / Metrics (BER/BLER) for training."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "base_uri": "https://localhost:8080/",
     "height": 178
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 20718,
     "status": "ok",
     "timestamp": 1532421271175,
     "user": {
      "displayName": "Dat Nguyen",
      "photoUrl": "//lh3.googleusercontent.com/-irIcNYd-KIw/AAAAAAAAAAI/AAAAAAAAAEs/NlM8kG6RL4Q/s50-c-k-no/photo.jpg",
      "userId": "108917076199533451784"
     },
     "user_tz": 420
    },
    "id": "abHjTgDgY5Zw",
    "outputId": "77a97512-7512-43ef-f9f4-c040df98d5ad"
   },
   "outputs": [],
   "source": [
    "def BLER(y, y_pred):\n",
    "    num_blocks_per_batch = tf.cast(tf.shape(y)[0], tf.int64)\n",
    "    hamming_distances =  tf.cast(tf.not_equal(y, tf.round(y_pred)), tf.int32)\n",
    "    return tf.count_nonzero(tf.reduce_sum(hamming_distances, axis=1)) \\\n",
    "            / num_blocks_per_batch\n",
    "\n",
    "model.compile('adam','binary_crossentropy', metrics=[BLER])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/20\n",
      "200/200 [==============================] - 103s 513ms/step - loss: 0.2496 - BLER: 0.5227 - val_loss: 7.3024e-04 - val_BLER: 0.0098\n",
      "Epoch 2/20\n",
      "200/200 [==============================] - 96s 479ms/step - loss: 4.1856e-04 - BLER: 0.0063 - val_loss: 2.9401e-04 - val_BLER: 0.0050\n",
      "Epoch 3/20\n",
      "200/200 [==============================] - 96s 481ms/step - loss: 1.8126e-04 - BLER: 0.0028 - val_loss: 1.4592e-04 - val_BLER: 0.0027\n",
      "Epoch 4/20\n",
      "200/200 [==============================] - 96s 482ms/step - loss: 1.1301e-04 - BLER: 0.0018 - val_loss: 8.8244e-05 - val_BLER: 0.0015\n",
      "Epoch 5/20\n",
      "200/200 [==============================] - 96s 481ms/step - loss: 8.3738e-05 - BLER: 0.0015 - val_loss: 6.4451e-05 - val_BLER: 8.5938e-04\n",
      "Epoch 6/20\n",
      "200/200 [==============================] - 96s 482ms/step - loss: 7.5286e-05 - BLER: 0.0015 - val_loss: 5.8278e-05 - val_BLER: 0.0013\n",
      "Epoch 7/20\n",
      "200/200 [==============================] - 96s 481ms/step - loss: 4.5110e-05 - BLER: 7.6172e-04 - val_loss: 4.3063e-05 - val_BLER: 9.3750e-04\n",
      "Epoch 8/20\n",
      "200/200 [==============================] - 96s 481ms/step - loss: 5.2885e-05 - BLER: 0.0012 - val_loss: 6.6503e-05 - val_BLER: 0.0018\n",
      "Epoch 9/20\n",
      "200/200 [==============================] - 96s 481ms/step - loss: 4.5853e-05 - BLER: 9.9609e-04 - val_loss: 4.8529e-05 - val_BLER: 0.0013\n",
      "Epoch 10/20\n",
      "200/200 [==============================] - 96s 482ms/step - loss: 4.0441e-05 - BLER: 6.6406e-04 - val_loss: 3.7920e-05 - val_BLER: 7.8125e-04\n",
      "Epoch 11/20\n",
      "200/200 [==============================] - 97s 483ms/step - loss: 4.0584e-05 - BLER: 9.1797e-04 - val_loss: 2.6834e-05 - val_BLER: 4.6875e-04\n",
      "Epoch 12/20\n",
      "200/200 [==============================] - 96s 482ms/step - loss: 3.6796e-05 - BLER: 9.9609e-04 - val_loss: 5.5099e-05 - val_BLER: 0.0016\n",
      "Epoch 13/20\n",
      "200/200 [==============================] - 96s 481ms/step - loss: 3.2873e-05 - BLER: 7.8125e-04 - val_loss: 3.2384e-05 - val_BLER: 0.0012\n",
      "Epoch 14/20\n",
      "200/200 [==============================] - 97s 483ms/step - loss: 3.9911e-05 - BLER: 8.0078e-04 - val_loss: 2.3435e-05 - val_BLER: 4.6875e-04\n",
      "Epoch 15/20\n",
      "200/200 [==============================] - 97s 486ms/step - loss: 2.4759e-05 - BLER: 5.4688e-04 - val_loss: 1.9895e-05 - val_BLER: 5.4688e-04\n",
      "Epoch 16/20\n",
      "200/200 [==============================] - 97s 487ms/step - loss: 3.0916e-05 - BLER: 9.3750e-04 - val_loss: 3.6851e-05 - val_BLER: 9.3750e-04\n",
      "Epoch 17/20\n",
      "200/200 [==============================] - 96s 481ms/step - loss: 3.5350e-05 - BLER: 9.9609e-04 - val_loss: 2.3776e-05 - val_BLER: 6.2500e-04\n",
      "Epoch 18/20\n",
      "200/200 [==============================] - 96s 482ms/step - loss: 2.2167e-05 - BLER: 6.8359e-04 - val_loss: 2.4353e-05 - val_BLER: 7.0312e-04\n",
      "Epoch 19/20\n",
      "200/200 [==============================] - 96s 481ms/step - loss: 3.3468e-05 - BLER: 7.8125e-04 - val_loss: 2.0634e-05 - val_BLER: 6.2500e-04\n",
      "Epoch 20/20\n",
      "200/200 [==============================] - 96s 482ms/step - loss: 1.7412e-05 - BLER: 4.1016e-04 - val_loss: 2.1044e-05 - val_BLER: 5.4688e-04\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.keras.callbacks.History at 0x7f103c98ef60>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "backup_model = tf.keras.callbacks.ModelCheckpoint('demod_decoder_snr8.hdf5', \n",
    "                                                  monitor='val_BLER',  # save model that has best BLER performance\n",
    "                                                  save_best_only=True)\n",
    "\n",
    "model.fit_generator(\n",
    "    generator= training_generator,\n",
    "    steps_per_epoch=200,\n",
    "    validation_data=(x_dev, y_dev),\n",
    "    callbacks=[backup_model],\n",
    "    epochs=20)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "7cdmmpKuWsFj"
   },
   "source": [
    "# Evaluate on multiple $SNRs$ at data_len = 100."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "base_uri": "https://localhost:8080/",
     "height": 439
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 31356,
     "status": "ok",
     "timestamp": 1532421417328,
     "user": {
      "displayName": "Dat Nguyen",
      "photoUrl": "//lh3.googleusercontent.com/-irIcNYd-KIw/AAAAAAAAAAI/AAAAAAAAAEs/NlM8kG6RL4Q/s50-c-k-no/photo.jpg",
      "userId": "108917076199533451784"
     },
     "user_tz": 420
    },
    "id": "6gA2Q4rH3T7R",
    "outputId": "aa0c078c-9184-4e79-9be9-7708d20da541"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SNR_dB = 0.000000\n",
      "\t[Modular]  Ber = 0.14168400 | Bler =0.99890000 - 7.58832s\n",
      "\t[Baseline] Ber = 0.21226500 | Bler =0.99960000 - 113.29151s\n",
      "SNR_dB = 3.000000\n",
      "\t[Modular]  Ber = 0.01626967 | Bler =0.60213333 - 6.89313s\n",
      "\t[Baseline] Ber = 0.04673367 | Bler =0.83133333 - 113.11327s\n",
      "SNR_dB = 5.000000\n",
      "\t[Modular]  Ber = 0.00118267 | Bler =0.08253333 - 6.92947s\n",
      "\t[Baseline] Ber = 0.00932500 | Bler =0.37660000 - 114.53455s\n",
      "SNR_dB = 7.000000\n",
      "\t[Modular]  Ber = 0.00003967 | Bler =0.00336667 - 7.12283s\n",
      "\t[Baseline] Ber = 0.00177667 | Bler =0.10513333 - 113.76675s\n",
      "SNR_dB = 10.000000\n",
      "\t[Modular]  Ber = 0.00000000 | Bler =0.00000000 - 75.27857s\n",
      "\t[Baseline] Ber = 0.00009133 | Bler =0.00646667 - 115.34519s\n",
      "SNR_dB = 12.000000\n",
      "\t[Modular]  Ber = 0.00000000 | Bler =0.00000000 - 6.95654s\n",
      "\t[Baseline] Ber = 0.00000367 | Bler =0.00026667 - 113.81340s\n"
     ]
    }
   ],
   "source": [
    "model.load_weights('demod_decoder_snr8.hdf5')\n",
    "\n",
    "class Params:\n",
    "    SNR_RANGE = [0.0, 3.0, 5.0, 7.0, 10.0, 12.0]\n",
    "    NUM_SAMPLES = 30000\n",
    "    BLOCK_LENGTH = 100\n",
    "   \n",
    "radio = RadioDataGenerator(Params.BLOCK_LENGTH, 10, 1, modulation_scheme='QPSK')\n",
    "\n",
    "ber_logs, bler_logs = [], []\n",
    "for i, snr in enumerate(Params.SNR_RANGE):\n",
    "    print('SNR_dB = %f' % snr)\n",
    "    x_test, y_test = next(radio.ecc_data_generator(1/1000, \n",
    "                                                   snr, \n",
    "                                                   batch_size=Params.NUM_SAMPLES, \n",
    "                                                   num_cpus=16, \n",
    "                                                   seed=2019))\n",
    "    \n",
    "    Y_test = np.squeeze(y_test, -1)  #(batch, 100, 1) to (batch, 100)\n",
    "    # Run Baseline/Neral Receiver\n",
    "    t1 = time.time()\n",
    "    with mp.Pool(mp.cpu_count()) as pool:\n",
    "        baseline_estimate = pool.map(baseline_receiver, [i.view(complex) for i in x_test])\n",
    "\n",
    "    t2 = time.time()\n",
    "    \n",
    "    predictions = model.predict(x_test, 500)\n",
    "    nn_estimate = np.squeeze(predictions, -1).round()\n",
    "    t3 = time.time()\n",
    "    \n",
    "    # Measure BER / BKER for two receivers\n",
    "    ber, bler       = get_ber_bler(np.array(baseline_estimate), Y_test)\n",
    "    nn_ber, nn_bler = get_ber_bler(nn_estimate, Y_test)\n",
    "    print('\\t[Modular]  Ber = {:.8f} | Bler ={:.8f} - {:3.5f}s'.format(nn_ber, nn_bler, t3 - t2))\n",
    "    print('\\t[Baseline] Ber = {:.8f} | Bler ={:.8f} - {:3.5f}s'.format(ber, bler, t2-t1))\n",
    "    \n",
    "    ber_logs.append([ber, nn_ber])\n",
    "    bler_logs.append([bler, nn_bler])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.savetxt('ber_decoder_bl100.txt', ber_logs)\n",
    "np.savetxt('bler_decoder_bl100.txt', bler_logs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Evaludate at data_len  =1000"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SNR_dB = 0.000000\n",
      "\t[Modular]  Ber = 0.13704160 | Bler =1.00000000 - 12.35305s\n",
      "\t[Baseline] Ber = 0.20038320 | Bler =1.00000000 - 194.99566s\n",
      "SNR_dB = 3.000000\n",
      "\t[Modular]  Ber = 0.01390300 | Bler =0.99960000 - 11.40209s\n",
      "\t[Baseline] Ber = 0.03448560 | Bler =1.00000000 - 194.08260s\n",
      "SNR_dB = 5.000000\n",
      "\t[Modular]  Ber = 0.00072580 | Bler =0.42680000 - 11.44237s\n",
      "\t[Baseline] Ber = 0.00385660 | Bler =0.83080000 - 197.03435s\n",
      "SNR_dB = 7.000000\n",
      "\t[Modular]  Ber = 0.00001060 | Bler =0.01000000 - 11.59086s\n",
      "\t[Baseline] Ber = 0.00025960 | Bler =0.14640000 - 195.21716s\n",
      "SNR_dB = 10.000000\n",
      "\t[Modular]  Ber = 0.00000000 | Bler =0.00000000 - 11.77438s\n",
      "\t[Baseline] Ber = 0.00000900 | Bler =0.00640000 - 196.30981s\n",
      "SNR_dB = 12.000000\n",
      "\t[Modular]  Ber = 0.00000000 | Bler =0.00000000 - 12.04506s\n",
      "\t[Baseline] Ber = 0.00000060 | Bler =0.00040000 - 195.41754s\n"
     ]
    }
   ],
   "source": [
    "class Params:\n",
    "    SNR_RANGE = [0.0, 3.0, 5.0, 7.0, 10.0, 12.0]\n",
    "    NUM_SAMPLES = 5000\n",
    "    BLOCK_LENGTH = 1000\n",
    "   \n",
    "radio = RadioDataGenerator(Params.BLOCK_LENGTH, 10, 1, modulation_scheme='QPSK')\n",
    "\n",
    "ber_logs, bler_logs = [], []\n",
    "for i, snr in enumerate(Params.SNR_RANGE):\n",
    "    print('SNR_dB = %f' % snr)\n",
    "    x_test, y_test = next(radio.ecc_data_generator(1/1000, \n",
    "                                                   snr, \n",
    "                                                   batch_size=Params.NUM_SAMPLES, \n",
    "                                                   num_cpus=16, \n",
    "                                                   seed=2019))\n",
    "    \n",
    "    Y_test = np.squeeze(y_test, -1)  #(batch, 100, 1) to (batch, 100)\n",
    "    # Run Baseline/Neral Receiver\n",
    "    t1 = time.time()\n",
    "    with mp.Pool(mp.cpu_count()) as pool:\n",
    "        baseline_estimate = pool.map(baseline_receiver, [i.view(complex) for i in x_test])\n",
    "\n",
    "    t2 = time.time()\n",
    "    \n",
    "    predictions = model.predict(x_test, 500)\n",
    "    nn_estimate = np.squeeze(predictions, -1).round()\n",
    "    t3 = time.time()\n",
    "    \n",
    "    # Measure BER / BKER for two receivers\n",
    "    \n",
    "    ber, bler       = get_ber_bler(np.array(baseline_estimate), Y_test)\n",
    "    nn_ber, nn_bler = get_ber_bler(nn_estimate, Y_test)\n",
    "    print('\\t[Modular]  Ber = {:.8f} | Bler ={:.8f} - {:3.5f}s'.format(nn_ber, nn_bler, t3 - t2))\n",
    "    print('\\t[Baseline] Ber = {:.8f} | Bler ={:.8f} - {:3.5f}s'.format(ber, bler, t2-t1))\n",
    "    \n",
    "    ber_logs.append([ber, nn_ber])\n",
    "    bler_logs.append([bler, nn_bler])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.savetxt('ber_decoder_bl1000.txt', ber_logs)\n",
    "np.savetxt('bler_decoder_bl1000.txt', bler_logs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "collapsed_sections": [],
   "default_view": {},
   "name": "RadioMLReceiver.ipynb",
   "provenance": [
    {
     "file_id": "https://github.com/datlife/radioml/blob/master/notebooks/Train_End2End_Reciver.ipynb",
     "timestamp": 1532370400196
    }
   ],
   "version": "0.3.2",
   "views": {}
  },
  "kernelspec": {
   "display_name": "Radio ML",
   "language": "python",
   "name": "radioml"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
